﻿//example   API限流 过滤器   
var config;
var redisclient; // redisclient.getclient() 可直接获取连接
module.exports = {
  grade: 0, //权重 值越小的优先执行
   check: function (req, res) { //检查是否符合过滤器要求  返回 true或false
    return true //所有请求 都执行限流策略
  },
  callback: function (req, res, next) { //符合过滤器要求的请求，会执行此回调, 给予通行时需调用next执行下一个过滤器
    let _this = this;
    let apikey = req.query.apikey || req.body && req.body.apikey;
    let method = req.query.method || req.body && req.body.method;//API接口名称。
    let tactics = config.apitactics;//api限流策略
    tactics = tactics.filter(function (v) {//取符合此次请求的限流策略
      if (v.servename == "ALL" || req.servername == v.servename) return true;
    })
    if (tactics.length) {
      let mult = redisclient.getclient().multi();//创建一个事务用来查询数据
      let day = new Date().totext("yyyy-MM-DD");
      let time = new Date().getTime();
      tactics.some(function (v) { //检查本地网关策略是否达到限流条件  
        if (v.qps == -1 && v.number == -1) {//跳过不限流的策略
          return false;
        }
        let rkey = "tactics:" + v.servename + ":";
        mult.hmget(rkey, "settime", "traffic_permit", day)//  读 上次令牌设置时间、 剩余可用的令牌(qps)、  当天已调用次数
      })

      if (apikey) {//读 apikey 的 自定义限流策略 
        if(!method){//未定义接口
          method="method"//避免为undfind时报错
        }
        mult.hmget("tactics:all:" + apikey, "settime", "traffic_permit", day, "type", method, "number", "sumnum", "qps", "sumnumjc")  // 上次令牌设置时间、 剩余可用的令牌(qps)、当天已调用次数 接口类型  接口名称 日调用上限 总调用上限 qps上限 总已调用次数
        mult.hmget("tactics:" + req.servername + ":" + apikey, "settime", "traffic_permit", day, "type", method, "number", "sumnum", "qps", "sumnumjc") //不存在的KEY  result会返回 完整结构的 null 
      }

      let P = new Promise(function (resolve, reject) {
        let mult2 = redisclient.getclient().multi();//创建一个事务用来更新数据
        mult.exec(function (err, result) {
          //console.log(err,result);
          let locai = apikey ? (result.length - 2) : -1;//本地策略数组长度
          if (!err) {
            let f = result.some(function (v, i) {
              let settime = v[0] || 0;// 此条策略上次设置令牌的时间 例 1585133843169
              if (i < locai || locai == -1) { //本地策略范围
                var number = tactics[i].number;//日调用上限
                var qps = tactics[i].qps;
                var sumnum = -1;
                var sumnumjc = -1;
                var type = "all";
                var methodauth = 1;//默认不限制 接口权限
                var rkey = "tactics:" + tactics[i].servename + ":";//+ (tactics[i].apitype ? "": apikey)
                var retd = {
                  succeed: 1,
                  server: tactics[i].servename,
                  tacticsid: i
                };
              } else {// API 自定义策略 
                var defaultr ={number:-1,sumnum:-1,qps:7,sumnumjc:0,type:"all"}//默认apikey限制策略,即此apikey在数据库中还未创建限流策略
                var number = parseInt(v[5]) || defaultr.number;//日调用上限
                var sumnum = parseInt(v[6]) || defaultr.sumnum;//总调用上限
                var qps = parseInt(v[7]) || defaultr.qps;// qps上限
                var sumnumjc = parseInt(v[8]) || defaultr.sumnumjc;//总已调用次数
                var type = v[3] || defaultr.type;//默认 拥有全部接口权限
                var methodauth = 1;
                if (type != "all") {//需接口判断权限
                  var methodauth = v[4] || 0; //0为没有此接口权限
                }
                var retd = {
                  succeed: 1,
                  server: result.length - 1 == i ? req.servername : "all",
                  tacticsid: i
                };
                var rkey = result.length - 1 == i ? ("tactics:" + req.servername + ":" + apikey) : "tactics:all:" + apikey
              }
              let interval_time = qps >= 10 ? 200 : 1000;
              let time_block = qps >= 10 ? 5 : 1;
              if (time - settime > interval_time || qps == -1) { //需要添加通行证，且此策略QPS可以放行
                if (number > 0 && v[2] > number) { //日调用次数达上限
                  retd.succeed = 0;
                  retd.messg = "此接口当日调用次数已达上限";
                } else if (sumnum > 0 && sumnumjc > sumnum) {
                  retd.succeed = 0;
                  retd.messg = "此接口总调用次数已用完";
                } else if (!methodauth) {
                  retd.succeed = 0;
                  retd.messg = "无此接口权限";
                }
                if (qps == -1) {
                  qps = 1000
                }
                let traper = (parseInt(v[1]) || 0) + parseInt(qps / time_block) - 1; //添加本次追加令牌数后的总令牌数
                if (traper > qps) {
                  traper = qps
                } else if (traper < 0) {
                  traper = 0
                }

                if (retd.succeed == 0) {
                  resolve(retd);
                  mult2.hmset(rkey, ["settime", time, "traffic_permit", traper])
                  return true;
                } else {
                  mult2.hmset(rkey, ["settime", time, "traffic_permit", traper, day, parseInt(v[2] || 0) + 1, "sumnumjc", sumnumjc + 1])
                }
              } else { //无需添加通行证，扣除一个令牌，增加一次日调用次数
                let f;
                if (parseInt(v[1]) <= 0) { //无可用令牌
                  retd.succeed = 0;
                  retd.messg = "QPS已达上限";
                } else if (!methodauth) {
                  retd.succeed = 0;
                  retd.messg = "无此接口权限";
                } else if (number > 0 && v[2] > number) { //日调用次数达上限
                  retd.succeed = 0;
                  retd.messg = "当日调用次数已达上限";
                } else if (sumnum > 0 && sumnumjc > sumnum) {
                  retd.succeed = 0;
                  retd.messg = "此接口总调用次数已用完";
                }
                if (retd.succeed == 0) {
                  resolve(retd);
                  return true;
                } else {
                  mult2.hmset(rkey, ["traffic_permit", v[1] - 1, day, parseInt(v[2] || 0) + 1, "sumnumjc", sumnumjc + 1]);
                }
              }
            })
            mult2.exec();
            if (!f) { //没有生效的策略
              resolve({
                succeed: 1
              }) //放行
            }
          }
        })
      })
      P.then(function (o) {
        if (!o.succeed) { //策略生效
          res.json(o);
        } else {
          next()
        }
      }).
        catch(function (err) {
          console.log(err);
          next()
        })
    } else {
      next()
    }
  },
  config: function (obj) { //如不需要读取配置 可不设置此属性
    config = obj
  },
  setredis: function (client) { //如不需要使用redis  可不设置此属性
    redisclient = client;
  }
}